/*
 * Serial communications module for Flying Fox
 *
 * Adapted from the MakingThings serial module
 * by Zik Saleeba 2008-03-10
 */

/*********************************************************************************

 Copyright 2006 MakingThings

 Licensed under the Apache License, 
 Version 2.0 (the "License"); you may not use this file except in compliance 
 with the License. You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0 
 
 Unless required by applicable law or agreed to in writing, software distributed
 under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 CONDITIONS OF ANY KIND, either express or implied. See the License for
 the specific language governing permissions and limitations under the License.

*********************************************************************************/

/* Library includes. */
#include <string.h>
#include <stdio.h>

/* Scheduler includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

/* Hardware specific headers. */
#include "board.h"
#include "AT91SAM7X256.h"
#include "lib_AT91SAM7X256.h"

#include "serial.h"
#include "serial_internal.h"


/* translation between port numbers and other useful things */
#define USART_PERIPHERAL(x) ((x)?AT91C_BASE_US1:AT91C_BASE_US0)
#define USART_ID(x) ((x)?AT91C_ID_US1:AT91C_ID_US0)


/* internal serial port state for each port */
static Serial_ Serial[NUM_SERIAL_CHANNELS];

/* prototypes */
static void Serial_Isr_Wrapper_Port0( void ) __attribute__ ((naked));
static void Serial_Isr_Wrapper_Port1( void ) __attribute__ ((naked));


/*
 * Serial provides a way to send and receive data via the serial port.
 *
 * The subsystem is supplied with small input and output buffers (of 100 
 * characters each) and at present the implementation is interrupt per 
 * character so it's not particularly fast.
 * 
 * Permits all of the common serial characteristics to be set including
 *
 * baud - the speed of the connection (110 - >2M) in baud or raw bits per 
 *        second.  9600 baud is the default setting.
 * 
 * bits - the size of each character (5 - 8).  8 bits is the default setting.
 * 
 * stopBits - the number of stop bits transmitted (1 or 2)  1 stop bit is the 
 *        default setting.
 * 
 * parity - the parity policy (-1 is odd, 0 is none and 1 is even).  Even is 
 *        the default setting.
 * 
 * hardwareHandshake - whether hardware handshaking is used or not.  
 *        HardwareHandshaking is off by default.
 *
 */


static inline portTickType Serial_TickwaitForTimeout(int timeout)
{
    /* set the timeout in ticks */
    if (timeout == 0)
        return 0;
    else if (timeout < 0)
        return portMAX_DELAY;
    else
        return timeout/portTICK_RATE_MS;
}


/*
 * NAME:        Serial_Write
 * ACTION:      Write a block of data to the Serial port.  Will block for the 
 *              time specified (in ms) if the queue fills up.
 * PARAMETERS:  int Port - the serial port to use (0 or 1)
 *              char *buffer - a pointer to the buffer to write from
 *              int count - an integer specifying the number of bytes to write
 *              int timeout - time in milliseconds to block waiting for the 
 *                  queue to free up. 0 means don't wait.
 * RETURNS:     0 on success, -1 on queue full and timed-out
 */
int Serial_Write(int Port, const char *buffer, int count, int timeout)
{
    portTickType tickwait;
        
    /* set the timeout in ticks */
    tickwait = Serial_TickwaitForTimeout(timeout);
    
    /* try to get write access */
    if (xSemaphoreTake(Serial[Port].writeLock, tickwait) != pdTRUE)
        return -1;

    /* send each character */
    while (count)
    {
        if (xQueueSend(Serial[Port].transmitQueue, buffer++, tickwait) != pdTRUE) 
            return -1; 
        count--;
    }
    
    /* Turn on the Tx interrupt so the ISR will remove the character from the 
     * queue and send it. This does not need to be in a critical section as 
     * if the interrupt has already removed the character the next interrupt 
     * will simply turn off the Tx interrupt again. */ 
    AT91F_US_EnableIt(USART_PERIPHERAL(Port), AT91C_US_TXRDY); 
    
    /* release write access */
    xSemaphoreGive(Serial[Port].writeLock);
    
    return 0;
}


/*
 * NAME:        Serial_Read
 * ACTION:      Read data from the Serial port.  Will block for the time 
 *              specified (in ms) if there are insufficient characters.  
 *              Blocking can be avoided if Serial_GetReadable() is used 
 *              to determine how many characters are available to read prior 
 *              to calling this function.
 * PARAMETERS:  int Port - which serial port to use (0 or 1)
 *              char *buffer - A pointer to the buffer to read into.
 *              int size - An integer specifying the maximum number of bytes to 
 *                          read
 *              int timeout - Time in milliseconds to block waiting for the 
 *                          specified number of bytes. 0 means don't wait.
 *                          -1 means wait indefinitely.
 * RETURNS:     int - number of bytes read (>=0)
 */
 
int Serial_Read(int Port, char *buffer, int size, int timeout)
{
    int count;
    int result = pdTRUE;
    portTickType tickwait;
    
    /* set the timeout in ticks */
    tickwait = Serial_TickwaitForTimeout(timeout);

    /* read the characters one by one */
    for (count = 0; count < size && result == pdTRUE; count++, buffer++)
    {
        /* wait for the character to be available in the receive queue */ 
        result = xQueueReceive(Serial[Port].receiveQueue, buffer, tickwait);
    }
    
    return count;
}


/*
 * NAME:        Serial_WaitSent
 * ACTION:      Wait until all pending data has been sent on the serial port
 * PARAMETERS:  int Port - the serial port to use (0 or 1)
 *              int timeout - time in milliseconds to block waiting for the 
 *                  queue to free up. 0 means don't wait.
 * RETURNS:     0 on success, -1 on error or timeout
 */
int Serial_WaitSent(int Port, int timeout)
{
    portTickType tickwait;
    
    /* set the timeout in ticks */
    tickwait = Serial_TickwaitForTimeout(timeout);

    Serial[Port].waitingOnTxQueueEmpty = true;
    if (xSemaphoreTake(Serial[Port].txQueueEmpty, tickwait) == pdTRUE)
        return 0;

    /* this is a hack. I don't know why it's necessary */    
	vTaskDelay(100/portTICK_RATE_MS);

    return -1;
}


/*
 * NAME:        Serial_GetReadable
 * ACTION:      Returns the number of bytes in the queue waiting to be read.
 * PARAMETERS:  int Port - which serial port to use (0 or 1)
 * RETURNS:     int - bytes in the receive queue
 */
 
int Serial_GetReadable(int Port)
{
    return uxQueueMessagesWaiting(Serial[Port].receiveQueue);
}


/*	
 * NAME:        Serial_SendChar
 * ACTION:      Sends a character (in the range of 0 to 255) to the write 
 *              queue. Will wait for space to become available if necessary.
 * PARAMETERS:  int Port - which serial port to use (0 or 1)
 *              int character - The character to be sent.  
 *                  Must be 0 <= c < 256.
 * RETURNS:     0 on success, -1 on queue full
 */
 
int Serial_SendChar(int Port, int character)
{
    /* try to get write access */
    if (xSemaphoreTake(Serial[Port].writeLock, portMAX_DELAY) != pdTRUE)
        return -1;

    if (character >= 0 && character < 256)
    {
        unsigned char c = (unsigned char)character;
        
        if (xQueueSend(Serial[Port].transmitQueue, &c, portMAX_DELAY) != pdTRUE) 
            return -1; 
            
        AT91F_US_EnableIt(USART_PERIPHERAL(Port), AT91C_US_TXRDY); 
    }
    
    /* release write access */
    xSemaphoreGive(Serial[Port].writeLock);
    
    return 0;
}


/*
 * NAME:        Serial_SetMode
 * ACTION:      Sets the serial baud rate and other modes
 * PARAMETERS:  int Port - which serial port to use (0 or 1)
 *              int baud - the desired baud rate (-1 to leave unchanged)
 *              int bits - the desired bits per word (-1 to leave unchanged)
 *              int parity - flag, set true to add a parity bit (-1 to leave unchanged)
 *              int stopBits - number of stop bits, 1 or 2 (-1 to leave unchanged)
 *              int hardwareHandshake - flag, set true for hardware 
 *                  handshaking (-1 to leave unchanged)
 */
 
void Serial_SetMode(int Port, int baud, int bits, int parity, int stopBits, int hardwareHandshake)
{
    AT91PS_USART USARTPeripheral;
    int USARTId;
    Serial_ *SerialDev;
    int mode;
    
    /* select the appropriate USART */
    USARTPeripheral = USART_PERIPHERAL(Port);
    USARTId = USART_ID(Port);
    SerialDev = &Serial[Port];

    /* stop the serial port while we change the parameters */
    AT91F_US_DisableTx(USARTPeripheral);
    AT91F_US_DisableRx(USARTPeripheral);
    AT91F_AIC_DisableIt(AT91C_BASE_AIC, USARTId);
    AT91F_US_DisableIt(USARTPeripheral, AT91C_US_RXRDY);
    
    /* change the settings we need to change */
    if (baud >= 0)
        SerialDev->baud = baud;
    
    if (bits >= 0)
    {
        if ( SerialDev->bits >= 5 && SerialDev->bits <= 8 )
            SerialDev->bits = bits;
        else
            SerialDev->bits = 8;
    }
    
    if (parity >= 0)
    {
        if ( parity >= -1 && parity <= 1 )
            SerialDev->parity = parity;
        else
            SerialDev->parity = 1;
    }
    
    if (stopBits >= 0)
    {
        if ( stopBits == 1 || stopBits == 2 )
            SerialDev->stopBits = stopBits;
        else
            SerialDev->stopBits = 1;
    }
    
    if (hardwareHandshake >= 0)
        SerialDev->hardwareHandshake = hardwareHandshake;
    
    mode = 
        ( ( SerialDev->hardwareHandshake ) ? AT91C_US_USMODE_HWHSH : AT91C_US_USMODE_NORMAL ) |
        ( AT91C_US_CLKS_CLOCK ) |
        ( ( ( SerialDev->bits - 5 ) << 6 ) & AT91C_US_CHRL ) |
        ( ( SerialDev->stopBits == 1 ) ? AT91C_US_NBSTOP_1_BIT : AT91C_US_NBSTOP_1_BIT ) |
        ( ( SerialDev->parity == 0 ) ? AT91C_US_PAR_NONE : ( ( SerialDev->parity == -1 ) ? AT91C_US_PAR_ODD : AT91C_US_PAR_EVEN ) );
    	
    AT91F_US_Configure(USARTPeripheral, MCK, mode, baud, 0);
    
    /* re-enable the UART */
    AT91F_US_EnableTx(USARTPeripheral);
    AT91F_US_EnableRx(USARTPeripheral);

    
    /* configure the interrupts */
    AT91F_AIC_ConfigureIt(AT91C_BASE_AIC, USARTId, 4, AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL, 
        (Port == 0) ? Serial_Isr_Wrapper_Port0 : Serial_Isr_Wrapper_Port1);

    AT91F_US_EnableIt(USARTPeripheral, AT91C_US_RXRDY);
    AT91F_AIC_EnableIt(AT91C_BASE_AIC, USARTId);
}


/*
 * NAME:        Serial_GetChar
 * ACTION:      Gets a single character from the queue or waits for one to
 *              arrive.
 * PARAMETERS:  int Port - which serial port to use (0 or 1)
 *              int timeout - Time in milliseconds to block waiting for the 
 *                  specified number of bytes. 0 means don't wait, just poll.
 *                  -1 means wait indefinitely.
 * RETURNS:     int - character form the queue having a value of 0 - 255.  
 *                  The return value is -1 if there was a timeout waiting
 *                  for a character to arrive.
 */
 
int Serial_GetChar(int Port, int timeout)
{
    unsigned char c;
    portTickType tickwait;
    
    /* set the timeout in ticks */
    if (timeout == 0)
    {
        tickwait = 0;
        
        /* check if there's anything in the queue */
        if (uxQueueMessagesWaiting(Serial[Port].receiveQueue) == 0)
            return -1;
    }
    else 
        tickwait = Serial_TickwaitForTimeout(timeout);

    if (xQueueReceive(Serial[Port].receiveQueue, &c, tickwait) == pdTRUE)
        return (int)c;
    else
        return -1;
}


/*
 * NAME:        Serial_Init_USART
 * ACTION:      Initialise one of the USARTs ready for serial activity
 * PARAMETERS:  int Port - which serial port to use (0 or 1)
 */

void Serial_Init_USART(int Port)
{
    AT91PS_USART USARTPeripheral;
    int USARTId;
    Serial_ *SerialDev;
    
    /* select the appropriate USART */
    USARTPeripheral = USART_PERIPHERAL(Port);
    USARTId = USART_ID(Port);
    SerialDev = &Serial[Port];
    
    /* set the default settings */
    SerialDev->baud = (Port == GPS_PORT) ? GPS_BAUD : SRADIO_BAUD;
    SerialDev->bits = 8;
    SerialDev->parity = false;
    SerialDev->stopBits = 1;
    SerialDev->hardwareHandshake = false;
    
    /* create the queues */
    SerialDev->receiveQueue = xQueueCreate(RECEIVE_BUFSIZE, 1); 
    SerialDev->transmitQueue = xQueueCreate(TRANSMIT_BUFSIZE, 1); 
    
    /* create the mutex lock semaphore */
    SerialDev->writeLock = xSemaphoreCreateMutex();
    
    /* create the "transmit queue empty" semaphore */
    SerialDev->waitingOnTxQueueEmpty = false;
    vSemaphoreCreateBinary(SerialDev->txQueueEmpty);
    
    /* most of the detail setting is done in here
     * also Resets TXRX and re-enables RX and turns on the interrupts */
    Serial_SetMode(Port, SerialDev->baud, SerialDev->bits, SerialDev->parity, SerialDev->stopBits, SerialDev->hardwareHandshake);
}


/*
 * NAME:        Serial_Init
 * ACTION:      Initialise the serial system
 */

void Serial_Init()
{
    int port;
    
    for (port = 0; port < NUM_SERIAL_CHANNELS; port++)
        Serial_Init_USART(port);
}


/*-----------------------------------------------------------*/


/*
 * NAME:        Serial_Isr_Handler
 * ACTION:      Interrupt service routine. Does the low-level moving
 *              of characters into and out of the serial port.
 */

static void Serial_Isr_Handler(int Port)
{
    unsigned long Status; 
    char SendChar; 
    char ReceiveChar; 
    portBASE_TYPE TaskWokenByTx = pdFALSE;
    portBASE_TYPE TaskWokenByEndTx = pdFALSE; 
    portBASE_TYPE TaskWokenByPost = pdFALSE; 
    AT91PS_USART USARTPeripheral;
    
    /* What caused the interrupt? */ 
    USARTPeripheral = USART_PERIPHERAL(Port);
    Status = USARTPeripheral->US_CSR & USARTPeripheral->US_IMR; 
    
    /* should we transmit? */
    if (Status & AT91C_US_TXRDY) 
    { 
        /* The interrupt was caused by the THR becoming empty. Are there any 
         * more characters to transmit? */ 
        if (xQueueReceiveFromISR(Serial[Port].transmitQueue, &SendChar, &TaskWokenByTx) == pdTRUE) 
        { 
            /* A character was retrieved from the queue so can be sent to the 
             * THR now. */ 
            USARTPeripheral->US_THR = SendChar; 
        } 
        else 
        {    
            /* Queue empty, nothing to send so turn off the Tx interrupt. */ 
            USARTPeripheral->US_IDR = AT91C_US_TXRDY;
            
            /* let a watcher know that we've finished sending */
            if (Serial[Port].waitingOnTxQueueEmpty)
                xSemaphoreGiveFromISR(Serial[Port].txQueueEmpty, TaskWokenByEndTx);
        }   
    } 
    
    /* can we receive? */
    if (Status & AT91C_US_RXRDY) 
    { 
        /* The interrupt was caused by a character being received. Grab the 
         * character from the RHR and place it in the queue of received  
         * characters. */ 
        int t = USARTPeripheral->US_RHR;
        ReceiveChar = t & 0xFF; 
//        ReceiveCount++;
        xQueueSendFromISR(Serial[Port].receiveQueue, &ReceiveChar, TaskWokenByPost); 
    } 
    
    /* complete the interrupt in the AIC */ 
    AT91C_BASE_AIC->AIC_EOICR = 0;
    
    /* do a task switch if necessary */
    if (TaskWokenByPost == pdTRUE || TaskWokenByTx == pdTRUE || TaskWokenByEndTx == pdTRUE)
        portYIELD_FROM_ISR();
}


/*
 * NAME:        Serial_Isr_Wrapper_Port0
 * ACTION:      The interrupt service routine is wrapped in this
 *              way as it can cause a context switch and FreeRTOS
 *              needs this little bit of help.
 */

static void Serial_Isr_Wrapper_Port0(void)
{
	/* Save the context of the interrupted task. */
	portSAVE_CONTEXT();

	/* Call the handler */
	Serial_Isr_Handler(0);

	/* Restore the context of whichever task is going to run next. */
	portRESTORE_CONTEXT();
}


/*
 * NAME:        Serial_Isr_Wrapper_Port1
 * ACTION:      The interrupt service routine is wrapped in this
 *              way as it can cause a context switch and FreeRTOS
 *              needs this little bit of help.
 */

static void Serial_Isr_Wrapper_Port1(void)
{
	/* Save the context of the interrupted task. */
	portSAVE_CONTEXT();

	/* Call the handler */
	Serial_Isr_Handler(1);

	/* Restore the context of whichever task is going to run next. */
	portRESTORE_CONTEXT();
}

